package com.summer.bilibili.socket;

import cn.hutool.core.map.BiMap;
import cn.hutool.extra.spring.SpringUtil;
import com.corundumstudio.socketio.*;
import com.corundumstudio.socketio.listener.ConnectListener;
import com.corundumstudio.socketio.listener.DisconnectListener;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.summer.bilibili.config.AppConfig;
import com.summer.bilibili.consts.Code;
import com.summer.bilibili.consts.TokenType;
import com.summer.bilibili.exception.CustomException;
import com.summer.bilibili.model.UserToken;
import com.summer.bilibili.util.JsonUtils;
import com.summer.bilibili.util.JwtUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.lang.Nullable;


import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 实现websocket的监听者基类
 * 实现了三种监听器功能
 * 连接监听（ConnectListener）
 * 断开监听（DisconnectListener）
 * 认证监听（AuthTokenListener）
 */
@SuppressWarnings("all") //抑制编译器对所有类型警告的提示
public interface SocketListener extends ConnectListener, // 连接监听
        DisconnectListener, // 断开监听
        AuthTokenListener // 认证监听
{
    // 日志记录器初始化
    public static final Logger log = LoggerFactory.getLogger ( SocketListener.class );

    /**
     * 双向map,存储 sessionId 和 用户id
     * 线程安全的双向映射：sessionId ↔ userId
     * 存储所有命名空间的会话信息（全局共享）
     */
    BiMap < String, Long > USER_INFOS = new BiMap <> ( new ConcurrentHashMap <> ( ) );

    /**
     * 命名空间名称,注入容器后会自动进行注册
     */
    String getNamespace ();

    /**
     * 是否需要登录后才能创建连接
     * 默认不强制 false
     */
    default boolean forceLogin () {
        return false;
    }

    /**
     * 获取命名空间服务
     *
     * @return 命名空间服务
     */
    default SocketIONamespace getNamespaceServer () {
        return SpringUtil.getBean ( SocketIOServer.class ).getNamespace ( getNamespace ( ) );
    }

    /**
     * 连接成功
     *
     * @param client
     */
    default void onConnect ( SocketIOClient client ) {
        log.info ( "socket connect sessionId={} ip={}" , client.getSessionId ( ) , client.getRemoteAddress ( ) );
    }

    /**
     * 断开连接
     *
     * @param client
     */
    default void onDisconnect ( SocketIOClient client ) {
        String sessionId = client.getSessionId ( ).toString ( );
        USER_INFOS.remove ( sessionId );
    }

    /**
     * 获取用户id
     *
     * @param client
     * @return 用户id
     */
    @Nullable
    default Long getUserId ( SocketIOClient client ) {
        String sessionId = client.getSessionId ( ).toString ( );
        return USER_INFOS.get ( sessionId );
    }

    /**
     * 获取sessionId
     *
     * @param userId
     * @return sessionId
     */
    @Nullable
    default String getSessionId ( Long userId ) {
        return USER_INFOS.getKey ( userId );
    }

    /**
     * 获取用户客户端
     *
     * @param userId
     * @return 用户客户端
     */
    @Nullable
    default SocketIOClient getClient ( Long userId ) {
        String sessionId = getSessionId ( userId );
        return sessionId != null ? getNamespaceServer ( ).getClient ( UUID.fromString ( sessionId ) ) : null;
    }

    /**
     * 发送事件
     *
     * @param event
     * @param userId
     * @param data
     */
    default void sendEvent ( String event , Long userId , Object... data ) {
        // 获取用户客户端
        SocketIOClient client = getClient ( userId );

        if ( client != null && data != null && data.length > 0 ) {
            // 获取JSON序列化器
            ObjectMapper objectMapper = SpringUtil.getBean ( ObjectMapper.class );
            // 序列化数据
            for ( int i = 0 ; i < data.length ; i++ ) {
                try {
                    data[ i ] = objectMapper.writeValueAsString ( data[ i ] );
                } catch (JsonProcessingException e) {
                    e.printStackTrace ( );
                    return;
                }
            }
            // 发送事件
            client.sendEvent ( event , data );
            log.info ( "socket send ip: {} data: {}" , client.getRemoteAddress ( ) , data );
        }
    }

    /**
     * 会在OnConnect之前触发
     * 如果返回失败的结果,则不会进入OnConnect而是onDisconnect
     */
    default AuthTokenResult getAuthTokenResult ( Object authToken , SocketIOClient client ) {
        // 获取当前会话id
        String sessionId = client.getSessionId ( ).toString ( );
        // 判断当前会话是否已经登录
        boolean isLogin = USER_INFOS.containsKey ( sessionId );

        // JWT令牌验证
        if ( !isLogin && authToken instanceof Map ) {
            Map < String, Object > authTokenMap = (Map < String, Object >) authToken;
            Object token = authTokenMap.get ( "token" );


            if ( token instanceof String ) {
                // 获取密钥
                AppConfig appConfig = SpringUtil.getBean ( AppConfig.class );
                String secret = appConfig.getToken ( ).get ( TokenType.REFRESH ).getSecret ( );

                try {
                    // 解析令牌
                    String subject = JwtUtils.parseJwt ( token.toString ( ) , secret ).getSubject ( );
                    UserToken userToken = JsonUtils.read ( subject , UserToken.class );

                    // 权限验证
                    if ( userToken.getUserType ( ) != 0 ) {
                        throw new CustomException ( Code.FORBIDDEN );
                    }

                    // 存储会话信息
                    USER_INFOS.put ( sessionId , userToken.getId ( ) );
                    isLogin = true;
                } catch (Exception e) {
                    e.printStackTrace ( );
                }
            }
        }

        // 强制登录检查
        if ( forceLogin ( ) && !isLogin ) {
            return new AuthTokenResult ( false , "未登录" );
        }

        return AuthTokenResult.AuthTokenResultSuccess;
    }
}
